/*

This file is part of roomyak.

Copyright (C) 2008 Pixecur Technologies Incorporated (Esotera).
Visit Esotera at http://www.esoteras3.com
 
roomyak is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
 
roomyak is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with roomyak; see the file COPYING.  If not, write to 
Pixecur Technologies, 213-31 Peet Street, St. John's, NL, A1B 3W8. 
 
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
 
*/

package com.datasentinel.util;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;

public class RunQueue implements Runnable
{
	private transient static Logger logger = Logger.getLogger(RunQueue.class.getName());
	private transient final Object enqueueMutex = new Object();
	private transient BlockingQueue<Runnable> queue;
	private transient Thread thumbnailRenderThread;
	private transient boolean cancel;
	private transient int numEnqueued;
	private transient int numProcessed;
	private transient int numNotProcessed;
	private transient long waitTime;
	
	public RunQueue()
	{
		this(1000L);
	}
	
	public RunQueue(long newWaitTime)
	{
		cancel = false;
		waitTime = newWaitTime;
		queue = new LinkedBlockingQueue<Runnable>();
	}
	public synchronized void enqueue(Runnable runnable)
	{
		if (!(thumbnailRenderThread instanceof Thread)
				|| !thumbnailRenderThread.isAlive() || cancel)
		{
			cancel = false;
			numEnqueued=0;
			numProcessed=0;
			numNotProcessed=0;
			logger.fine("starting new thread to process queue items");
			thumbnailRenderThread = new Thread(this, getClass().getSimpleName());
			thumbnailRenderThread.start();
		}
		try
		{
			logger.finer("enqueuing " + runnable);
			queue.put(runnable);
			numEnqueued++;
			synchronized(enqueueMutex)
			{
				enqueueMutex.notify();
			}
		}
		catch (InterruptedException ie)
		{
		}
	}
	
	public synchronized boolean isCancelled()
	{
		return cancel;
	}

	public synchronized void cancel()
	{
		logger.fine("cancel requested");
		if (thumbnailRenderThread instanceof Thread && thumbnailRenderThread.isAlive())
		{
			logger.fine("interrupting " + thumbnailRenderThread);
			cancel = true;
			numNotProcessed = queue.size();
			queue.clear();
			thumbnailRenderThread.interrupt();
		}
		else
		{
			logger.info("cancel requested but no thread is running; ignoring.");
		}
	}
	
	public void processQueueItem() throws InterruptedException
	{
		Runnable runnable = queue.take();
		numProcessed++;
		logger.finer("queue item [" + runnable
				+ "] taken from queue, executing runnable");
		runnable.run();
	}

	public void run()
	{
		do
		{
			if (isCancelled())
			{
				logger.fine("cancel is set, breaking from loop");
				break;
			}
			try
			{
				processQueueItem();
				Thread.yield();
			}
			catch (InterruptedException ie)
			{
				logger.fine("thread interrupted, breaking from loop.");
				break;
			}
			
			if(!isCancelled() && queue.size() < 1)
			{
				long timeOut = System.currentTimeMillis()+waitTime;
				synchronized(enqueueMutex)
				{
					try
					{
						if(waitTime > 0)
						{
							logger.finer("queue is now empty, waiting "+waitTime+" msecs for new queue items...");
							enqueueMutex.wait(waitTime);
							if(System.currentTimeMillis() < timeOut)
							{
								logger.finer("new queue item before timeout reached");
							}
							else
							{
								logger.finer("queue still empty and timeout reached");
							}
						}
						else
						{
							logger.finer("no wait time set. not waiting for new queue items.");
						}
					}
					catch(InterruptedException ie)
					{
						break;
					}
				}
			}
		} while (!queue.isEmpty());
		logger.info(String.format("items <queued=%d, processed=%d, skipped=%d>",
				numEnqueued, numProcessed, numNotProcessed));
	}
}
